package com.uziot.bucket.common.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author shidt
 * @version V1.0
 * @className ClassLoaderUtils
 * @date 2020-05-11 14:23
 * @description 类加载器工具类
 */
public class ClassLoaderUtils {

    private static final Logger logger = LoggerFactory.getLogger(ClassLoaderUtils.class);

    private static final Map<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>();

    /**
     * 加载Java类。 使用全限定类名
     *
     * @param className 全类名
     * @return 类
     */
    public static Class<?> loadClass(String className) throws ClassNotFoundException {
        Class<?> clazz;
        if (CLASS_CACHE.containsKey(className)) {
            return CLASS_CACHE.get(className);
        }
        clazz = ClassUtils.forName(className, getClassLoader());
        CLASS_CACHE.put(className, clazz);
        return clazz;
    }

    /**
     * 实例化对象
     *
     * @param classImpl classImpl
     * @return 对象
     * @throws InstantiationException e
     * @throws IllegalAccessException e
     * @throws ClassNotFoundException e
     */
    public static Object newInstance(String classImpl) throws
            InstantiationException, IllegalAccessException, ClassNotFoundException {
        return loadClass(classImpl).newInstance();
    }


    public static Object newInstance(String classImpl, Class<?>[] pType, Object[] obj) throws
            InstantiationException, IllegalAccessException, ClassNotFoundException,
            IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
        return loadClass(classImpl).getConstructor(pType).newInstance(obj);
    }

    /**
     * 得到类加载器
     *
     * @return ClassLoader
     */
    public static ClassLoader getClassLoader() {
        return ClassLoaderUtils.class.getClassLoader();
    }

    /**
     * 得到当前ClassLoader
     *
     * @return ClassLoader
     */
    public static ClassLoader getCurrentClassLoader() {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = ClassLoaderUtils.class.getClassLoader();
        }
        return cl == null ? ClassLoader.getSystemClassLoader() : cl;
    }

    /**
     * 得到当前ClassLoader
     *
     * @param clazz 某个类
     * @return ClassLoader
     */
    public static ClassLoader getClassLoader(Class<?> clazz) {
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        if (loader != null) {
            return loader;
        }
        if (clazz != null) {
            loader = clazz.getClassLoader();
            return loader;
        }
        return ClassLoader.getSystemClassLoader();
    }

    /**
     * 提供相对于classpath的资源路径，返回文件的输入流
     *
     * @param relativePath 必须传递资源的相对路径
     *                     是相对于classpath的路径。如果需要查找classpath外部的资源，需要使用../来查找
     * @return 文件输入流
     */
    public static InputStream getStream(String relativePath)
            throws MalformedURLException, IOException {
        if (!relativePath.contains("../")) {
            return getClassLoader().getResourceAsStream(relativePath);

        } else {
            return ClassLoaderUtils.getStreamByExtendResource(relativePath);
        }
    }

    /**
     * @param url url
     * @return stream
     */
    public static InputStream getStream(URL url) throws IOException {
        if (url != null) {

            return url.openStream();

        } else {
            return null;
        }
    }

    /**
     * @param relativePath 必须传递资源的相对路径
     *                     是相对于classpath的路径。如果需要查找classpath外部的资源，需要使用../来查找
     * @return InputStream
     */
    public static InputStream getStreamByExtendResource(String relativePath) throws IOException {
        return ClassLoaderUtils.getStream(ClassLoaderUtils
                .getExtendResource(relativePath));
    }

    /**
     * 提供相对于classpath的资源路径，返回属性对象，它是一个散列表
     *
     * @param resource resource
     * @return properties
     */
    public static Properties getProperties(String resource) {
        InputStream in;
        try {
            in = getStream(resource);
            if (null == in) {
                in = new BufferedInputStream(new FileInputStream(resource));
            }
        } catch (IOException e) {
            throw new RuntimeException("couldn't load properties file '"
                    + resource + "'", e);
        }
        return getProperties(in);
    }

    /**
     * 提供相对于classpath的资源路径，返回属性对象，它是一个散列表
     *
     * @param in in
     * @return p
     */
    public static Properties getProperties(InputStream in) {
        Properties properties = new Properties();
        try {
            properties.load(in);
        } catch (IOException e) {
            throw new RuntimeException("couldn't load properties inputStream",
                    e);
        }

        return properties;
    }

    /**
     * 得到本Class所在的ClassLoader的Classpat的绝对路径。 URL形式的
     *
     * @return String
     */
    public static String getAbsolutePathOfClassLoaderClassPath() {
        ClassLoader classLoader = ClassLoaderUtils.getClassLoader();
        URL resource = classLoader.getResource("");
        assert resource != null;
        String classPath = resource.toString();
        logger.info(classPath);
        return classPath;
    }

    /**
     * @param relativePath 必须传递资源的相对路径。是相对于classpath的路径。如果需要查找classpath外部的资源，需要使用. ./来查找
     * @return 资源的绝对URL
     * @throws MalformedURLException e
     */
    public static URL getExtendResource(String relativePath) throws MalformedURLException {
        URL resourceabsoluteurl = null;
        logger.info("A relative path to the incoming:" + relativePath);
        if (!relativePath.contains("../")) {
            resourceabsoluteurl = ClassLoaderUtils.getResource(relativePath);
            logger.info("URL:" + resourceabsoluteurl);
            return resourceabsoluteurl;
        }
        String classPathAbsolutePath = ClassLoaderUtils
                .getAbsolutePathOfClassLoaderClassPath();
        if ("/".equals(relativePath.substring(0, 1))) {
            relativePath = relativePath.substring(1);
        }
        logger.info(String.valueOf(relativePath.lastIndexOf("../")));

        String wildcardString = relativePath.substring(0,
                relativePath.lastIndexOf("../") + 3);
        relativePath = relativePath
                .substring(relativePath.lastIndexOf("../") + 3);
        int containSum = ClassLoaderUtils.containSum(wildcardString, "../");
        classPathAbsolutePath = ClassLoaderUtils.cutLastString(
                classPathAbsolutePath, "/", containSum);
        String resourceAbsolutePath = classPathAbsolutePath + relativePath;
        logger.info("Absolute path:" + resourceAbsolutePath);
        logger.info("URL:" + resourceabsoluteurl);
        resourceabsoluteurl = new URL(resourceAbsolutePath);
        logger.info("URL:" + resourceabsoluteurl);
        return resourceabsoluteurl;
    }

    /**
     * @param source source
     * @param dest   dest
     * @return int
     */
    public static int containSum(String source, String dest) {
        int containSum = 0;
        int destLength = dest.length();
        while (source.contains(dest)) {
            containSum = containSum + 1;
            source = source.substring(destLength);

        }

        return containSum;
    }

    /**
     * @param source source
     * @param dest   dest
     * @param num    num
     * @return s
     */
    public static String cutLastString(String source, String dest, int num) {
        for (int i = 0; i < num; i++) {
            source = source.substring(0,
                    source.lastIndexOf(dest, source.length() - 2) + 1);

        }

        return source;
    }

    public static URL getResource(String resource) {
        return ClassLoaderUtils.getClassLoader().getResource(resource);
    }

    public static String getHome() {
        File f = new File(ClassLoaderUtils.getResource("").getFile());
        return f.getParentFile().getParent();
    }

    /**
     * 根据类名加载Class
     *
     * @param className 类名
     * @return Class
     * @throws ClassNotFoundException 找不到类
     */
    public static Class<?> forName(String className)
            throws ClassNotFoundException {
        return forName(className, true);
    }

    /**
     * 根据类名加载Class
     *
     * @param className  类名
     * @param initialize 是否初始化
     * @return Class
     * @throws ClassNotFoundException 找不到类
     */
    public static Class<?> forName(String className, boolean initialize) throws ClassNotFoundException {
        return Class.forName(className, initialize, getCurrentClassLoader());
    }

    /**
     * 通过参数获取
     *
     * @param args args
     * @return static
     */
    public static Class<?>[] getClazzByArgs(Object[] args) {
        Class<?>[] parameterTypes = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ArrayList) {
                parameterTypes[i] = List.class;
                continue;
            }
            if (args[i] instanceof LinkedList) {
                parameterTypes[i] = List.class;
                continue;
            }
            if (args[i] instanceof HashMap) {
                parameterTypes[i] = Map.class;
                continue;
            }
            if (args[i] instanceof Long) {
                parameterTypes[i] = long.class;
                continue;
            }
            if (args[i] instanceof Double) {
                parameterTypes[i] = double.class;
                continue;
            }
            if (args[i] instanceof TimeUnit) {
                parameterTypes[i] = TimeUnit.class;
                continue;
            }
            parameterTypes[i] = args[i].getClass();
        }
        return parameterTypes;
    }

    public Method getMethod(Class<?> classType, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
        return classType.getMethod(methodName, parameterTypes);
    }
}